/*
 * MIT License
 *
 * Copyright (c) 2024-2048 冰羽
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package cn.star.framework.sql.type;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import java.math.BigDecimal;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.JDBCType;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import lombok.Getter;

/**
 * Java字段类型映射Jdbc字段类型<br>
 * {@link JDBCType}、{@link Types}
 *
 * @author zhaoweiping
 *     <p style='color: red'>Created on 2024-09-23 11:06:00
 * @since 3.0.0
 */
public enum JdbcMappingType {

  /** Identifies the generic SQL type {@code BIT}. */
  BIT(JDBCType.BIT),
  /** Identifies the generic SQL type {@code TINYINT}. */
  TINYINT(JDBCType.TINYINT, byte.class, Byte.class),
  /** Identifies the generic SQL type {@code SMALLINT}. */
  SMALLINT(JDBCType.SMALLINT, short.class, Short.class),
  /** Identifies the generic SQL type {@code INTEGER}. */
  INTEGER(JDBCType.INTEGER, int.class, Integer.class),
  /** Identifies the generic SQL type {@code BIGINT}. */
  BIGINT(JDBCType.BIGINT, long.class, Long.class),
  /** Identifies the generic SQL type {@code FLOAT}. */
  FLOAT(JDBCType.FLOAT, float.class, Float.class),
  /** Identifies the generic SQL type {@code REAL}. */
  REAL(JDBCType.REAL),
  /** Identifies the generic SQL type {@code DOUBLE}. */
  DOUBLE(JDBCType.DOUBLE, double.class, Double.class),
  /** Identifies the generic SQL type {@code NUMERIC}. */
  NUMERIC(JDBCType.NUMERIC),
  /** Identifies the generic SQL type {@code DECIMAL}. */
  DECIMAL(JDBCType.DECIMAL, BigDecimal.class),
  /** Identifies the generic SQL type {@code CHAR}. */
  CHAR(JDBCType.CHAR, char.class, Character.class),
  /** Identifies the generic SQL type {@code VARCHAR}. */
  VARCHAR(JDBCType.VARCHAR, String.class),
  /** Identifies the generic SQL type {@code LONGVARCHAR}. */
  LONG_VARCHAR(JDBCType.LONGVARCHAR),
  /** Identifies the generic SQL type {@code DATE}. */
  DATE(JDBCType.DATE, Date.class),
  /** Identifies the generic SQL type {@code TIME}. */
  TIME(JDBCType.TIME, Time.class),
  /** Identifies the generic SQL type {@code TIMESTAMP}. */
  TIMESTAMP(JDBCType.TIMESTAMP, Timestamp.class),
  /** Identifies the generic SQL type {@code BINARY}. */
  BINARY(JDBCType.BINARY),
  /** Identifies the generic SQL type {@code VARBINARY}. */
  VARBINARY(JDBCType.VARBINARY),
  /** Identifies the generic SQL type {@code LONGVARBINARY}. */
  LONG_VARBINARY(JDBCType.LONGVARBINARY),
  /** Identifies the generic SQL value {@code NULL}. */
  NULL(JDBCType.NULL),
  /**
   * Indicates that the SQL type is database-specific and gets mapped to a Java object that can be
   * accessed via the methods getObject and setObject.
   */
  OTHER(JDBCType.OTHER),
  /**
   * Indicates that the SQL type is database-specific and gets mapped to a Java object that can be
   * accessed via the methods getObject and setObject.
   */
  JAVA_OBJECT(JDBCType.JAVA_OBJECT),
  /** Identifies the generic SQL type {@code DISTINCT}. */
  DISTINCT(JDBCType.DISTINCT),
  /** Identifies the generic SQL type {@code STRUCT}. */
  STRUCT(JDBCType.STRUCT),
  /** Identifies the generic SQL type {@code ARRAY}. */
  ARRAY(JDBCType.ARRAY),
  /** Identifies the generic SQL type {@code BLOB}. */
  BLOB(JDBCType.BLOB, Blob.class),
  /** Identifies the generic SQL type {@code CLOB}. */
  CLOB(JDBCType.CLOB, Clob.class),
  /** Identifies the generic SQL type {@code REF}. */
  REF(JDBCType.REF),
  /** Identifies the generic SQL type {@code DATALINK}. */
  DATA_LINK(JDBCType.DATALINK),
  /** Identifies the generic SQL type {@code BOOLEAN}. */
  BOOLEAN(JDBCType.BOOLEAN, boolean.class, Boolean.class),

  /* JDBC 4.0 JDBCType */

  /** Identifies the SQL type {@code ROWID}. */
  ROW_ID(JDBCType.ROWID),
  /** Identifies the generic SQL type {@code NCHAR}. */
  NCHAR(JDBCType.NCHAR),
  /** Identifies the generic SQL type {@code NVARCHAR}. */
  NVARCHAR(JDBCType.NVARCHAR),
  /** Identifies the generic SQL type {@code LONGNVARCHAR}. */
  LONG_NVARCHAR(JDBCType.LONGNVARCHAR),
  /** Identifies the generic SQL type {@code NCLOB}. */
  NCLOB(JDBCType.NCLOB),
  /** Identifies the generic SQL type {@code SQLXML}. */
  SQL_XML(JDBCType.SQLXML),

  /* JDBC 4.2 JDBCType */

  /** Identifies the generic SQL type {@code REF_CURSOR}. */
  REF_CURSOR(JDBCType.REF_CURSOR),

  /** Identifies the generic SQL type {@code TIME_WITH_TIMEZONE}. */
  TIME_WITH_TIMEZONE(JDBCType.TIME_WITH_TIMEZONE),

  /** Identifies the generic SQL type {@code TIMESTAMP_WITH_TIMEZONE}. */
  TIMESTAMP_WITH_TIMEZONE(JDBCType.TIMESTAMP_WITH_TIMEZONE);

  @Getter private final JDBCType jdbcType;
  @Getter private final Class<?>[] types;

  JdbcMappingType(JDBCType jdbcType, Class<?>... types) {
    this.jdbcType = jdbcType;
    this.types = types;
  }

  public static JDBCType getJdbcType(Class<?> type) {
    JdbcMappingType[] types = JdbcMappingType.values();
    for (JdbcMappingType jdbcMappingType : types) {
      Class<?>[] classes = jdbcMappingType.getTypes();
      if (ArrayUtil.isNotEmpty(classes)) {
        for (Class<?> clazz : classes) {
          if (clazz.isAssignableFrom(type)) {
            return jdbcMappingType.getJdbcType();
          }
        }
      }
    }

    return null;
  }

  public static List<Class<?>> getJavaTypes(JDBCType type) {
    List<Class<?>> list = new ArrayList<>();
    JdbcMappingType[] types = JdbcMappingType.values();
    for (JdbcMappingType jdbcMappingType : types) {
      if (jdbcMappingType.getJdbcType() == type) {
        Class<?>[] classes = jdbcMappingType.getTypes();
        if (ArrayUtil.isNotEmpty(classes)) {
          list = CollectionUtil.newArrayList(classes);
          break;
        }
      }
    }

    return list;
  }
}
